package com.gf.framework.mongo.service;

import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.gf.framework.common.model.Page;
import com.gf.framework.common.model.user.UserContext;
import com.gf.framework.common.utils.SpringContextUtil;
import com.gf.framework.idgenetor.config.IdGenetorConfig;
import com.gf.framework.idgenetor.config.SnowFlakeIdGentetor;
import com.gf.framework.mongo.entity.BaseEntity;
import com.gf.framework.mongo.lambda.MongoLambdaQuery;
import com.mongodb.bulk.BulkWriteInsert;
import com.mongodb.bulk.BulkWriteResult;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.BulkOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.io.Serializable;
import java.util.*;

/**
 * @author gaofei
 * @date 2022/5/28 9:36
 */
public abstract class GfMongoTemplate<T extends BaseEntity> implements SmartInitializingSingleton {
    @Autowired
    protected MongoTemplate mongoTemplate;

    @Autowired
    protected IdGenetorConfig idGenetorConfig;

    private final Class<T> classzz = currentModelClass();

    private final String collectionName = currentCollectionName();

    private SnowFlakeIdGentetor snowFlakeIdGentetor;

    public Class<T> getEntityClass() {
        return classzz;
    }

    @SuppressWarnings("unchecked")
    private Class<T> currentModelClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), GfMongoTemplate.class, 0);
    }

    private String currentCollectionName() {
        try {
            Document document = classzz.getDeclaredAnnotation(Document.class);
            if (StringUtils.isBlank(document.collection())) {
                String collectionName = classzz.getSimpleName();
                return collectionName.substring(0,1).toLowerCase() + collectionName.substring(1);
            }
            return document.collection();
        } catch (Exception e) {
            String collectionName = classzz.getSimpleName();
            return collectionName.substring(0,1).toLowerCase() + collectionName.substring(1);
        }
    }

    public String getCollectionName() {
        return collectionName;
    }

    @Override
    public void afterSingletonsInstantiated() {
        snowFlakeIdGentetor = SpringContextUtil.getBean(IdGenetorConfig.class).
                getSnowFlakeIdGentetorByTableName(getCollectionName());
    }

    public T save(T entity) {
        entity.setId(nextId());
        entity.setCreateUser(UserContext.getUserId());
        return mongoTemplate.save(entity, getCollectionName());
    }

    public List<BulkWriteInsert> saveBatch(List<T> entityList) {
        for (T entity: entityList) {
            entity.setId(nextId());
            entity.setCreateUser(UserContext.getUserId());
        }

        BulkWriteResult bulkWriteResult =
                mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED,
                        getCollectionName()).insert(entityList).execute();

        return bulkWriteResult.getInserts();
    }

    public DeleteResult removeById(Long id) {
        return mongoTemplate.remove(
                new Query(Criteria.where("id").is(id)), getCollectionName());
    }

    public BulkWriteResult removeByIds(Collection<Long> idList) {
        List<Query> queryList = new ArrayList<>(idList.size());
        for (Serializable id : idList) {
            queryList.add(new Query(Criteria.where("id").is(id)));
        }

        return mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED,
                getCollectionName()).remove(queryList).execute();
    }

    public MongoLambdaQuery<T> lambdaQuery(SFunction<T, Object> column) {
        return MongoLambdaQuery.where(this, column);
    }

    public MongoLambdaQuery<T> lambdaQuery(String column) {
        return MongoLambdaQuery.where(this, column);
    }

    public MongoLambdaQuery<T> lambdaQuery(Criteria criteria) {
        return MongoLambdaQuery.where(this, criteria);
    }

    public boolean updateById(Long id, Update update) {
        UpdateResult result = mongoTemplate.updateFirst(
                new Query(Criteria.where("id").is(id)), update, getCollectionName());
        return result.getModifiedCount() > 0;
    }

    public boolean updateFirst(Query query, Update update) {
        UpdateResult result = mongoTemplate.updateFirst(query, update, getCollectionName());
        return result.getModifiedCount() > 0;
    }

    public boolean update(Query query, Update update) {
        UpdateResult result = mongoTemplate.updateMulti(query, update, getCollectionName());
        return result.getModifiedCount() > 0;
    }

    public T findOne(Query query) {
        return mongoTemplate.findOne(query, getEntityClass(), getCollectionName());
    }

    public List<T> find(Query query) {
        return mongoTemplate.find(query, getEntityClass(), getCollectionName());
    }

    public DeleteResult remove(Query query) {
        return mongoTemplate.remove(query, getCollectionName());
    }

    public T findAndRemove(Query query) {
        return mongoTemplate.findAndRemove(query, getEntityClass(), getCollectionName());
    }

    public List<T> findAllAndRemove(Query query) {
        return mongoTemplate.findAllAndRemove(query, getEntityClass(), getCollectionName());
    }

    public long count(Query query) {
        return mongoTemplate.count(query, getCollectionName());
    }

    private Long nextId() {
        return snowFlakeIdGentetor.nextId();
    }
}
